//
// Manta - Structural Variant and Indel Caller
// Copyright (c) 2013-2019 Illumina, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//

/// \file
/// \author Chris Saunders
///

#pragma once

#include <array>
#include <cassert>
#include <iosfwd>

#include "svgraph/GenomeInterval.hpp"

/// \brief Categorize the nature of the evidence used to infer an SV candidate (anomalous read pair, CIGAR
/// string, etc...)
namespace SVEvidenceType {
enum index_t {
  PAIR,         ///< An anomalous read pair observation based on both of the read pair's alignment records
  LOCAL_PAIR,   ///< An anomalous read pair observation inferred from the information in only one of the read
                ///< pair's alignment records
  CIGAR,        ///< A large indel found in a single read
  SOFTCLIP,     ///< A large soft-clipped region on one edge of a single read
  SEMIALIGN,    ///< A large poorly-aligned region on one edge of a single read
  SHADOW,       ///< A read with an unmapped mate read
  SPLIT_ALIGN,  ///< A read alignment which is split between two or more locations using the BAM spec 'SA'
                ///< format
  UNKNOWN,      ///< Temporary initialization state
  SIZE
};

inline const char* label(const int i)
{
  switch (i) {
  case PAIR:
    return "pair";
  case LOCAL_PAIR:
    return "local_pair";
  case CIGAR:
    return "cigar";
  case SOFTCLIP:
    return "softclip";
  case SEMIALIGN:
    return "semialign";
  case SHADOW:
    return "shadow";
  case SPLIT_ALIGN:
    return "split_align";
  case UNKNOWN:
    return "unknown";
  default:
    assert(false && "Unknown SVCandidate evidence type");
    return nullptr;
  }
}

inline bool isPairType(const int i)
{
  switch (i) {
  case PAIR:
  case LOCAL_PAIR:
    return true;
  default:
    return false;
  }
}
}  // namespace SVEvidenceType

/// enumerate candidate evidence counts:
struct SVBreakendLowResEvidence {
  typedef SVBreakendLowResEvidence self_t;

  SVBreakendLowResEvidence() { clear(); }

  SVBreakendLowResEvidence(const self_t& rhs)
  {
    clear();
    merge(rhs);
  }

  const self_t& operator=(const self_t& rhs)
  {
    if (this == &rhs) return *this;

    clear();
    merge(rhs);
    return *this;
  }

  unsigned getVal(const int i) const
  {
    assert((i >= 0) && (i < SVEvidenceType::SIZE));
    return _evidence[i];
  }

  unsigned getTotal() const
  {
    unsigned sum(0);
    for (int i(0); i < SVEvidenceType::SIZE; ++i) {
      sum += _evidence[i];
    }
    return sum;
  }

  void clear()
  {
    for (int i(0); i < SVEvidenceType::SIZE; ++i) _evidence[i] = 0;
  }

  void add(const int i, const unsigned count = 1)
  {
    assert((i >= 0) && (i < SVEvidenceType::SIZE));
    _evidence[i] += count;
  }

  void merge(const self_t& rhs)
  {
    for (int i(0); i < SVEvidenceType::SIZE; ++i) {
      _evidence[i] += rhs._evidence[i];
    }
  }

private:
  std::array<unsigned short, SVEvidenceType::SIZE> _evidence;
};

std::ostream& operator<<(std::ostream& os, const SVBreakendLowResEvidence& sce);

namespace SVBreakendState {
enum index_t {
  UNKNOWN,     ///< Everything else not covered below
  RIGHT_OPEN,  ///< 5'/left side of breakend is mapped, 3'/right side of the breakend is mapped elsewhere
  LEFT_OPEN,   ///< 3'/right side of breakend is mapped, 5'/left side of the breakend is mapped elsewhere
  COMPLEX  ///< A typical small scale assembly locus -- something is happening in a small region, the event
           ///< might be local to that region but we don't know
};

inline const char* label(const index_t idx)
{
  switch (idx) {
  case UNKNOWN:
    return "UNKNOWN";
  case RIGHT_OPEN:
    return "RIGHT_OPEN";
  case LEFT_OPEN:
    return "LEFT_OPEN";
  case COMPLEX:
    return "COMPLEX";
  default:
    return "UNKNOWN";
  }
}

/// return true if this is a 'normal' breakend with a known orientation
///
/// a false return should typically be for a region targeted for local assembly, but without a specific
/// variant hypothesis
inline bool isSimpleBreakend(const index_t idx)
{
  return ((idx == RIGHT_OPEN) || (idx == LEFT_OPEN));
}

inline bool isSameOrientation(const index_t idx1, const index_t idx2)
{
  if (!isSimpleBreakend(idx1)) return false;
  if (!isSimpleBreakend(idx2)) return false;
  return (idx1 == idx2);
}

inline bool isOppositeOrientation(const index_t idx1, const index_t idx2)
{
  if (!isSimpleBreakend(idx1)) return false;
  if (!isSimpleBreakend(idx2)) return false;
  return (idx1 != idx2);
}

inline bool isInnies(const bool isIdx1First, const index_t idx1, const index_t idx2)
{
  if (isIdx1First) {
    return ((idx1 == RIGHT_OPEN) && (idx2 == LEFT_OPEN));
  } else {
    return ((idx2 == RIGHT_OPEN) && (idx1 == LEFT_OPEN));
  }
}

inline bool isOutties(const bool isIdx1First, const index_t idx1, const index_t idx2)
{
  return isInnies((!isIdx1First), idx1, idx2);
}

}  // namespace SVBreakendState

struct SVBreakend {
  typedef SVBreakend self_t;

  SVBreakend() : state(SVBreakendState::UNKNOWN) {}

  bool isIntersect(const self_t& rhs) const
  {
    if (state != rhs.state) return false;
    if (SVBreakendState::UNKNOWN == state) return true;
    return interval.isIntersect(rhs.interval);
  }

  bool operator<(const self_t& rhs) const
  {
    if (state < rhs.state) return true;
    if (state == rhs.state) {
      return (interval < rhs.interval);
    }
    return false;
  }

  bool merge(const SVBreakend& rhs, const bool isExpandRegion)
  {
    if (!isIntersect(rhs)) return false;
    if (isExpandRegion) {
      interval.range.merge_range(rhs.interval.range);
    }
    lowresEvidence.merge(rhs.lowresEvidence);
    return true;
  }

  void clear()
  {
    interval.clear();
    state = SVBreakendState::UNKNOWN;
    lowresEvidence.clear();
  }

  unsigned getPairCount() const { return lowresEvidence.getVal(SVEvidenceType::PAIR); }

  unsigned getLocalPairCount() const { return lowresEvidence.getVal(SVEvidenceType::LOCAL_PAIR); }

  /// return true if there is pair evidence, but it is only local
  bool isLocalPairOnly() const { return ((getLocalPairCount() > 0) && (getPairCount() == 0)); }

  /// return true if all evidence for this breakend is local
  bool isLocalOnly() const { return (getLocalPairCount() == lowresEvidence.getTotal()); }

  unsigned getAnyNonPairCount() const
  {
    using namespace SVEvidenceType;

    unsigned sum(0);
    for (int i(0); i < SVEvidenceType::SIZE; ++i) {
      if (i == PAIR) continue;
      if (i == LOCAL_PAIR) continue;
      if (i == UNKNOWN) continue;
      sum += lowresEvidence.getVal(i);
    }
    return sum;
  }

  // include any evidence type which defines a two-region hypothesis
  unsigned getSpanningCount() const
  {
    using namespace SVEvidenceType;

    return (lowresEvidence.getVal(PAIR) + lowresEvidence.getVal(CIGAR) + lowresEvidence.getVal(SPLIT_ALIGN));
  }

  pos_t getLeftSideOfBkptAdjustment() const
  {
    using namespace SVBreakendState;
    switch (state) {
    case RIGHT_OPEN:
      return 0;
    case LEFT_OPEN:
      return -1;
    default:
      return 0;
    }
  }

public:
  // if ! isPrecise() the interval is the X% confidence interval of the SV breakend, the interface allows for
  // various probability distributions to back this interval, but these must be accessed via
  // SVCandidate:
  //
  // csaunders 07-2015: observation of what's here rather than a policy description (where is the design
  // discussion for this?) if isPrecise(), the the left end of the pos is the leftmost *mapped* base when the
  // variant is fully left-shifted wrt this breakend. this means that the left most position concept is
  // different for the right open and left-open cases. for right-open, this is the base before the breakend,
  // for left-open this is the base after.
  //
  GenomeInterval           interval;
  SVBreakendState::index_t state;

  SVBreakendLowResEvidence lowresEvidence;
};

std::ostream& operator<<(std::ostream& os, const SVBreakend& svb);
